#include "form_main.h"
#include "form_welcome.h"
#include "form_setting.h"
#include "form_qst.h"
#include "qevent.h"
#include "ui_form_main.h"

form_Main::form_Main(QWidget *parent)
    : QMainWindow(parent), ui(new Ui::form_Main) {
    ui->setupUi(this);

    bOk1 = 0;
    connected = 0;
    drawn = 0;
    painted = 0;
    //fqst.setParent(this);

}

form_Main::~form_Main() { delete ui; }

/**
 * @brief form_Main::安装配置
 * @param st 配置
 */
void form_Main::setup_ini(setting* st){
    QString qbuf;                                   //文本框内容
    GETQSTRWS(2*FILENAME_MAX,qbuf,st->problemfd);
    ui->txt_qst_path->setText(qbuf);
    GETQSTRWS(2*FILENAME_MAX,qbuf,st->userfd);
    ui->txt_stus_path->setText(qbuf);

    displayfile_doqst();                            //装入文件
}

//绘制部分窗体
void form_Main::drawMe(){

    if(drawn)return;                                //不要重复绘画
    drawn = 1;

    ui->stk_whole->setCurrentIndex(0);              //回到主页

    QString weltxt;                                 //欢迎语(html)

    //用户情况讨论
    switch(this->lginfo.user.type){
    case Student:
        ui->action_statisics->setDisabled(1);       //学生不能看详细统计数据
        ui->tabw_qst->setTabEnabled(1,0);           //学生不能录题 防止刷分
        ui->action_save->setDisabled(1);            //学生不需要保存学生链表
        weltxt = "<b>禁用权限</b>：禁用了详细统计数据按钮、禁用了录题选项卡和禁用了保存学生链表按钮\n";
        break;
    case Teacher:
        ui->tabw_qst->setTabEnabled(0,0);           //教师不能做题
        weltxt = "<b>禁用权限</b>：禁用了做题选项卡";
        break;
    case Admin:                                     //管理者拥有所有权限
        weltxt = "<b>禁用权限</b>：无禁用权限";
        break;
    case not_identified:                            //未知身份者是游客 只有看主页的份
    case guest:
        ui->action_save->setDisabled(1);
        ui->action_statisics->setDisabled(1);
        ui->action_quiz->setDisabled(1);
        weltxt = "<b>可用权限</b>：主页";
        break;
    }


    //题目 按表格内容自适应
    ui->tree_doqst->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
    ui->tree_stat->header()->setSectionResizeMode(QHeaderView::ResizeToContents);

    //主页
    QString cbuf;
    GETQSTRWS(2*NAMELENMAX,cbuf,this->lginfo.user.username);
    usertype thetype = this->lginfo.user.type;
    weltxt = QString("您好 %1 <font color=\"red\">%2</font>！<br>").arg(getStrFromType(thetype),cbuf) + weltxt;
    ui->txtweb_welcome->setHtml(weltxt);


    setup_ini(&this->set);       //安装当前配置

    //学生管理
    //读取文件 装入程序中学生链表 这个过程不能省略
    int ret = read_students_stu(&liststu_head,&liststu_tail);
    if(ret == -1){
        //读取失败
        QMessageBox::critical(0,"错误","未能读取学生信息");
        return;
    }
    liststu_n = ret <= 0 ? 0 : ret - 1;                                //不算首行
    liststu_head_find = 0;                              //筛选列表初始化
    liststu_tail_find = 0;
    liststu_n_find = 0;                                 //继续筛选
    liststu_head_ff = 0;
    liststu_tail_ff = 0;
    liststu_n_ff = 0;

    if(liststu_n == 0){
        //没有学生数据
        QMessageBox::warning(0,"警告","没有学生数据");
    }

    on_cbo_stus_find_currentTextChanged("无筛选"); //通过响应显示学生表

}

//链接基本控件
void form_Main::connectAll(){

    if(connected)return;        //不要重复链接/绘画
    connected = 1;

    connect(ui->action_Menu,&QAction::triggered,this,[&](){
        ui->stk_whole->setCurrentIndex(0);
    });

    connect(ui->action_quiz,&QAction::triggered,this,[&](){
        ui->stk_whole->setCurrentIndex(1);
    });

    connect(ui->action_statisics,&QAction::triggered,this,[&](){
        ui->stk_whole->setCurrentIndex(2);
        QMessageBox::warning(0,"注意","原则上学号不允许重复");
    });

    connect(ui->action_reset__R_,&QAction::triggered,this,[&](){
        //重设配置文件
        ASKFORSETTING(fst,this);
        if(fst.changed){
            //重新登录
            Form_Welcome* fw = new Form_Welcome;
            fw->show();
            this->close();
        }

    });

    connect(ui->action_save,&QAction::triggered,this,[&](){
        save_liststu();
    });

    //此处取地址会报错 Qt自身bug
    connect(&fqst,SIGNAL(send_save_grd(QString&,QString&)),this,SLOT(get_save_grd(QString&,QString&)));
    connect(&fqst,SIGNAL(qsted_closed()),this,SLOT(get_qsted_closed()));


}



//做题页--文件夹路径有误
void form_Main::invaildpath_doqst(){

    //提示用户
    ui->tree_doqst->clear();
    ui->tree_doqst->addTopLevelItem(new QTreeWidgetItem({"请检查文件夹路径"}));
}


//做题页--展示文件夹下文件并设置控件
void form_Main::displayfile_doqst(){

    QDir d(ui->txt_qst_path->text());

    if(!d.exists()){
        //路径有误
        invaildpath_doqst();
        return;
    }

    ui->txt_qst_path->setText(d.absolutePath());    //转化为绝对路径显示

    //根据文件夹装入题目
    auto flist = d.entryInfoList(QDir::Files | QDir::NoDotAndDotDot | QDir::Dirs,QDir::SortFlag::DirsFirst);
    auto it = flist.begin();
    ui->tree_doqst->setColumnCount(2);
    ui->tree_doqst->clear();


    /**
     *          此处可递归优化
    */

    for(;it != flist.end();++it){
        //遍历

        //只想要*.html和文件夹
        if(it->suffix() == "html" || it->isDir() ){
            auto nItem = new QTreeWidgetItem;
            nItem->setText(0,it->fileName());
            nItem->setText(1,it->isDir() ? "文件夹" : "题目文件");
            ui->tree_doqst->addTopLevelItem(nItem);
            //如果是文件夹多展开一级
            if(it->isDir()){
                //同理
                QDir dd(it->filePath());
                auto fl = dd.entryInfoList(QDir::Files | QDir::NoDotAndDotDot);
                for(auto x : fl){
                    if(x.suffix() != "html")continue;
                    auto nn = new QTreeWidgetItem;
                    nn->setText(0,x.fileName());
                    nn->setText(1,"题目文件");
                    nItem->addChild(nn);
                }

            }

        }


    }
}

/**
 * @brief 获得班级完整名称
 * @param maj 专业名
 * @param cls 班级编号
 * @param yrs 年级编号
 * @return
 */
QString form_Main::getclsname(char* maj,int cls,int yrs){
    QString fr = "%1级%2%3班";
    QString c;
    GETQSTRMB(c,maj);
    return fr.arg(QString::number(yrs),c,QString::number(cls));
}

/**
 * @brief 表头添加进统计数据
 * @details 散列 二次散列
 * @param maj 专业名
 * @param cls 班级编号
 * @param yrs 年级编号
 */
void form_Main::addtosta(char* maj,int cls,int yrs){
    //散列 该参数测试通过

    //年级
    if(!hta_yrs[yrs]){
        hta_yrs[yrs] = yrs;
        auto yrsroot = ui->tree_stat->topLevelItem(0);
        auto nt = new QTreeWidgetItem({QString::number(yrs)});
        yrsroot->addChild(nt);
        ui->tree_stat->setItemWidget(nt,9,new QComboBox);      //存数据用
    }

    //专业
    int hs = hashStr(maj,100,17);
    if(!hashtable_maj[hs]){
        hashtable_maj[hs] = 1;
        QString c;
        GETQSTRMB(c,maj);
        auto majroot = ui->tree_stat->topLevelItem(1);
        auto nt = new QTreeWidgetItem({c});
        majroot->addChild(nt);
        ui->tree_stat->setItemWidget(nt,9,new QComboBox);
    }

    //班级全称：年级专业班级编号
    int hs1,hs2;
    hs1 = hashStr(maj,1000,cls);
    hs2 = hashStr(maj,1000,yrs);
    hs = hashStr(maj,1000,hs1+hs2);     //二次散列h(maj,cls,yrs)

    if(!hashtable_cls[hs]){
        hashtable_cls[hs] = 1;
        auto clsroot = ui->tree_stat->topLevelItem(2);
        auto nt = new QTreeWidgetItem({getclsname(maj,cls,yrs)});   //获得班级完整名称
        clsroot->addChild(nt);
        ui->tree_stat->setItemWidget(nt,9,new QComboBox);
    }



}

/**
 * @brief 统计表头初始化
 */
void form_Main::init_stat(){
    //统计初始化
    memset(hashtable_maj,0,sizeof(hashtable_maj));              //散列表清零
    memset(hta_yrs,0,sizeof(hta_yrs));
    memset(hashtable_cls,0,sizeof(hashtable_cls));

    ui->tree_stat->clear();                                     //绘制统计表表头
    auto qtt = new QTreeWidgetItem;
    qtt->setText(0,"年级");
    ui->tree_stat->addTopLevelItem(qtt);
    qtt = new QTreeWidgetItem;
    qtt->setText(0,"专业");
    ui->tree_stat->addTopLevelItem(qtt);
    qtt = new QTreeWidgetItem;
    qtt->setText(0,"班级");
    ui->tree_stat->addTopLevelItem(qtt);
    qtt = new QTreeWidgetItem;
    qtt->setText(0,"总体");
    ui->tree_stat->addTopLevelItem(qtt);

    auto gg = new QTreeWidgetItem;
    qtt->addChild(gg);
    ui->tree_stat->setItemWidget(gg,9,new QComboBox);

    ui->tree_stat->hideColumn(9);                               //隐藏数据
}



/**
 * @brief 读取一行数据 分析并赋值给学生reciv 进行合法性检查
 * @param rowstr 行数据
 * @param [out] reciv 学生数据指针
 * @param [in] alert 是否要提醒
 * @return 是否成功
 */
int form_Main::read_stu_line(std::string rowstr,student* reciv,int alert){
    if(!reciv)return 0;

    //string变长数组
    stringVec vt;
    stdStrSplit(rowstr,',',vt);

    //学号,姓名,专业,年级,班级,作答记录（点击超链接查看）,总正确率（%）
    //qlst[5]是作答记录 将作为链表装入
    if(vt.size() < 7)return 0;
    STU_ASSIGN_ID(reciv,strtoll(vt[0].c_str(),0,10),alert);
    strcpy(reciv->name,vt[1].c_str());
    strcpy(reciv->major,vt[2].c_str());
    STU_ASSIGN_YEAR(reciv,strtoll(vt[3].c_str(),0,10),alert);
    STU_ASSIGN_CLASS(reciv,strtoll(vt[4].c_str(),0,10),alert);
    STU_ASSIGN_GRADE(reciv,strtoll(vt[6].c_str(),0,10),alert);


    //记录链表。。。
    stringVec rlst;
    stdStrSplit(vt[5],'|',rlst);

    node *rh,*rt;
    list_headtail(&rh,&rt);
    auto it = rlst.begin();

    for(;it != rlst.end();++it){
        node* newrec = list_push_back(nodetype::filename,rt);
        NODE_FILENAME(recf,newrec);                                //文件名链表
        strcpy(recf,it->c_str());

    }
    reciv->recfiles = rh;
    return 1;
}


/**
 * @brief 学生管理--读学生文件 然后变为链表装入程序
 * @param [out] head    链表头节点
 * @param [out] tail    链表尾节点
 * @return 返回students.csv的文件行数 失败返回-1
 */
int form_Main::read_students_stu(node** head,node** tail){
    if(!head || !tail)return -1;

    char fuser[2*FILENAME_MAX] = {0};
    getUserFilename(&this->set,FILENAME_STUDENTS,fuser);        //学生文件路径

    int sz = sizeFile(fuser);
    if(sz < 0){
        QMessageBox::critical(0,"错误","文件不存在或被占有！");
        return -1;                   //文件不存在或被占有
    }
    list_headtail(head,tail);           //创建空链表
    if(sz == 0){
        //文件为空
        return 0;
    }

    FILE* fp = fopen(fuser,"r");

    if(!fp){
        return -1;
    }

    char t;
    int row = 0;
    std::string rowstr;
    //由于每行大小未知 使用String帮助动态内存管理 此时String中的内容是ANSI 不过非中文字符不受影响

    while((t = fgetc(fp)) != EOF){

        if(t == '\n'){
            row++;
            if(row <= 1){rowstr.clear();continue;}                  //第1行表头不看
            //不会有换行出现

            node* newstu = list_push_back(nodetype::stu,*tail);     //往链表后插入新节点
            NODE_STUDENT(reciv,newstu);                             //强制转换

            int ret = read_stu_line(rowstr,reciv,0);                //根据一行文件内容分析赋值给学生指针
            if(!ret){
                //如果读取失败
                fclose(fp);
                return -1;
            }


            rowstr.clear();                                         //清空行缓冲
        }
        rowstr += t;


    }


    fclose(fp);
    return row;
}




/**
 * @brief 学生管理--将链表内容写入文件之中
 * @param head 链表之头
 * @param tail 链表之尾
 */
void form_Main::list_toFile_stu(FILE* fp,node* head,node* tail){
    if(!head || !tail || !head->next || !tail->last || head->next == tail){
        QMessageBox::critical(0,"错误","学生数据不存在");
        return;
    }


    node* it = head->next;          //第一个有效节点
    rewind(fp);
    //写表头

    fprintf(fp,"%s",QString("学号,姓名,专业,年级,班级,作答记录（点击查看）,总正确率（%）\n").toLocal8Bit().data());  //本地编码转换

    while(it->con){
        //遍历学生链表
        NODE_STUDENT(reciv,it);
        std::string oprec = getStrFromRecL(reciv->recfiles);
        fprintf(fp,"%lld,%s,%s,%d,%d,%s,%d\n",reciv->ID,reciv->name,reciv->major,reciv->year,reciv->cls,oprec.c_str(),reciv->grd);
        it = it->next;
    }


}


/**
 * @brief form_Main::tablestu_insEmptyLine
 * @param i 行
 * @param [out] insBut 插入按钮
 * @param [out] delBut 删除按钮
 * @param [out] recCBox 记录组合框
 * @param able 单元格能否编辑
 */
void form_Main::tablestu_insEmptyLine(int i,QPushButton** insBut,QPushButton** delBut,QComboBox** recCBox,int able = 1){
    ui->table_stus->insertRow(i);

    auto ib = new QPushButton("插入");    //插入按钮
    auto db = new QPushButton("删除");    //删除按钮
    QComboBox* rccb = new QComboBox;                      //记录组合框

    //图标
    (ib)->setIcon(QIcon(":/new/prefix1/rc/open1.png"));
    (db)->setIcon(QIcon(":/new/prefix1/rc/del1.png"));

    //设置初值

    ui->table_stus->setCellWidget(i,0,ib);
    ui->table_stus->setCellWidget(i,1,db);
    ui->table_stus->setCellWidget(i,7,rccb);

    connect(db,&QPushButton::clicked,this,&form_Main::onClick_tableDel);    //链接回调
    connect(ib,&QPushButton::clicked,this,&form_Main::onClick_tableIns);

    //筛选时的插入无意义
    if(ui->but_stus_findctrl->text() == "开始查找" && ui->cbo_stus_find->currentText() != "无筛选")
        (ib)->setDisabled(1);

    //继续筛选时的删除 数据结构暂不允许
    if(bOk1)
        db->setDisabled(1);

    if(!able){
        //不可编辑
        for(int j = 2;j < ui->table_stus->columnCount();++j){
            ui->table_stus->setItem(i,j,new QTableWidgetItem);
            ui->table_stus->item(i,j)->setFlags(Qt::NoItemFlags);
        }
    }

    if(insBut)*insBut = ib;
    if(delBut)*delBut = db;
    if(recCBox)*recCBox = rccb;

}


/**
 * @brief 学生管理--将链表显示到表格上
 * @param head  链表头
 * @param tail  链表尾
 * @param row   每页几行
 * @param page  第几页(1开始)
 */
void form_Main::list_toTable_stu(node* head,node* tail,int row,int page){

    ui->table_stus->blockSignals(1);            //阻断信号

    //学生管理表绘制
    ui->table_stus->setRowCount(0);         //清空
    ui->table_stus->clear();

    ui->table_stus->setColumnCount(9);
    QStringList sl{"插入(前)","删除","学号","姓名","专业","年级","班级","作答记录（点击查看）","总正确率（%）"};
    ui->table_stus->setHorizontalHeaderLabels(sl);

    if(liststu_n == 0){
        //删完了
        QPushButton *a,*b;
        tablestu_insEmptyLine(0,&a,&b,0,0); //加个空行
        b->setDisabled(1);
        return;
    }

    if(!head || !tail)return;
    if(head->next == tail)return;                               //空列表

    node* it = head->next;
    it = advanceNode(it,row*page,0,0);                    //按页移动到相应位置
    setbuf(stdout,0);
    int i = 0;
    for(;i < row && it->con != 0;++i){
        QPushButton *insBut,*delBut;QComboBox *recCBox;
        tablestu_insEmptyLine(i,&insBut,&delBut,&recCBox);  //插入空行

        //装入学生数据
        NODE_STUDENT(stud,it);
        ui->table_stus->setItem(i,1,new QTableWidgetItem(QString::number(stud->ID)));
        ui->table_stus->setItem(i,2,new QTableWidgetItem(QString::number(stud->ID)));
        QString qb;
        //printf("%s\n",stud->name);
        GETQSTRMB(qb,stud->name);

        ui->table_stus->setItem(i,3,new QTableWidgetItem(qb));
        GETQSTRMB(qb,stud->major);
        ui->table_stus->setItem(i,4,new QTableWidgetItem(qb));
        ui->table_stus->setItem(i,5,new QTableWidgetItem(QString::number(stud->year)));
        ui->table_stus->setItem(i,6,new QTableWidgetItem(QString::number(stud->cls)));
        ui->table_stus->setItem(i,8,new QTableWidgetItem(QString::number(stud->grd)));

        //装入Combo记录
        node* recit = stud->recfiles;
        if(recit && recCBox){
            //有东西才装
            recit = recit->next;
            recCBox->addItem("");
            while(recit->con){
                NODE_FILENAME(fn,recit);
                GETQSTRMB(qb,fn);
                recCBox->addItem(qb);
                recit = recit->next;
            }
            connect(recCBox,SIGNAL(currentIndexChanged(int)),this,SLOT(onSelect_tablecsvCombo(int)));
        }

        it = it->next;
    }

    // QPushButton *lastdel,*lastins;
    // tablestu_insEmptyLine(i,&lastins,&lastdel,0,0); //加个空行
    // lastdel->setDisabled(1);
    ui->table_stus->blockSignals(0);
}

/**
 * @brief 根据行，页进行表的重载
 * @head,tail 链表头尾
 */
void form_Main::table_resize_stu(node* head,node* tail){

    ui->table_stus->blockSignals(1);            //阻断信号

    int rpp = ui->spin_stu_rowspp->value();     //每页几行
    int p = ui->spin_stu_page->value()-1;       //第几页
    ui->spin_stu_rowspp->setMaximum(30);

    //计算学生表页数 分有无筛选讨论
    if(ui->cbo_stus_find->currentText() == "无筛选" || ui->but_stus_findctrl->text() == "停止查找"){
        tablestu_page = liststu_n / rpp;
        if(liststu_n % rpp != 0){
            tablestu_page++;
        }
    }
    else{
        tablestu_page = liststu_n_find / rpp;
        if(liststu_n_find % rpp != 0){
            tablestu_page++;
        }
    }


    ui->spin_stu_page->setMaximum(tablestu_page);
    if(tablestu_page != 0)ui->spin_stu_page->setMinimum(1);

    list_toTable_stu(head,tail,rpp,p);

    ui->table_stus->blockSignals(0);
}


/**
 * @brief 从在表中的第几行推算出在链表中的第几个
 * @param row 行
 * @return
 */
int form_Main::calc_listn_row(int row){
    int rpp = ui->spin_stu_rowspp->value();     //每页几行
    int p = ui->spin_stu_page->value()-1;       //第几页
    return rpp * p + row;                       //链表中的第几个
}


/**
 * @brief 学生管理--根据ID找学生节点
 * @param head,tail链表
 * @param ID
 * @return 学生节点 没找到是 0
 */
node* form_Main::findstu_ID(node* head,node* tail,long long finderid){
    if(!head || !tail)return 0;
    if(head->next == tail)return 0; //空链表

    node* k = head->next;
    while(k->con){
        NODE_STUDENT(st,k);
        if(st->ID == finderid){
            break;
        }
        k = k->next;
    }

    if(k->con == 0)return 0;
    return k;
}

/**
 * @brief 学生管理--根据行数找学生节点
 * @param head,tail链表
 * @param row 行数
 * @return 学生节点 没找到是 0
 */
node* form_Main::findstu_row(node* head,node* tail,int row){
    if(!head || !tail)return 0;
    if(head->next == tail)return 0; //空链表

    auto w = ui->table_stus->item(row,1);           //1的学号是原来的学号
    if(!w)return 0;
    long long finderid = w->text().toLongLong();    //根据学号找原来的位置

    return findstu_ID(head,tail,finderid);          //返回位置
}


/**
 * @brief 把学生数据更新成表格中的数据 带合法性检查
 * @param stud 学生数据
 * @param row 行
 * @param c 列
 */
void form_Main::replacestu_rc(student* stud,int row,int column,bool alert){
    if(!stud)return;
    auto kk = ui->table_stus->item(row,column);
    if(!kk)return;
    QString aft = kk->text();
    switch(column){
    case 2:{
        //ID
        STU_ASSIGN_ID(stud,aft.toLongLong(),alert);
        break;
    }
    case 3:{
        //name
        GETMBQSTR(stud->name,aft);
        break;
    }
    case 4:{
        //major
        GETMBQSTR(stud->major,aft);
        break;
    }
    case 5:{
        //入学年份
        STU_ASSIGN_YEAR(stud,aft.toInt(),alert);
        break;
    }
    case 6:{
        //班级
        STU_ASSIGN_CLASS(stud,aft.toInt(),alert);
        break;
    }
    case 8:{
        //总正确率
        STU_ASSIGN_GRADE(stud,aft.toInt(),alert);
        break;
    }
    default:
        break;
    }
}

/**
 * @brief 学生管理--判断学生信息是否符合筛选条件
 * @param stud 学生信息
 * @param tt 筛选类别范围
 * @param arg1 用户提供筛选参数
 * @return 是否符合筛选条件
 */
int form_Main::liststu_suitable(student* stud,const QString& tt,const QString& arg1){
    if(!stud)return 0;
    QString sc;
    long long a = 0,b = 0;
    int ok = 0;
    if(tt == "姓名"){
        GETQSTRMB(sc,stud->name);
        ok = sc.contains(arg1);    //模糊查询
    }else if(tt == "专业"){
        GETQSTRMB(sc,stud->major);
        ok = sc.contains(arg1);
    }else{
        if(arg1.contains(" ")){
            //区间查找
            auto nn = arg1.split(" ");
            if(nn.size() < 2)return 0;
            a = nn[0].toLongLong();
            b = nn[1].toLongLong();
            if(a > b){
                //保持顺序
                int t = a;
                a = b;
                b = t;
            }

        }else{
            //精确找数
            a = b = arg1.toLongLong();
        }

    }

    if(tt == "学号"){
        ok = a <= stud->ID && stud->ID <= b;
    }else if(tt == "年级"){
        ok = a <= stud->year && stud->year <= b;
    }else if(tt == "班级"){
        ok = a <= stud->cls && stud->cls <= b;
    }else if(tt == "总正确率"){
        ok = a <= stud->grd && stud->grd <= b;
    }
    return ok;
}

/**
 * @brief 学生管理--判断学生信息是否符合筛选条件 默认版
 * @param stud 学生数据
 * @attention 范围参数框是空的时候代表不筛选
 * @return 是否符合筛选条件
 */
int form_Main::liststu_suitable(student* stud){
    QString s = ui->txt_stus_find->text();
    if(s == "")return 1;
    return liststu_suitable(stud,ui->cbo_stus_find->currentText(),s);
}


/**
 * @brief 学生管理--根据链表筛选
 * @param srchead 源链表头
 * @param srctail 源链表尾
 * @param [out] newhead 结果链表头
 * @param [out] newtail 结果链表尾
 * @param notf 指定为 1 时 不进行任何筛选
 * @return 链表节点数 无效返回-1
 */
int form_Main::liststu_find(node* srchead,node* srctail,node** newhead,node** newtail,int notf = 0){

    if(!newhead || !newtail)return -1;
    if(!srchead || !srctail)return -1;

    list_headtail(newhead,newtail);   //创建一个新的
    node* it = srchead->next;

    int n = 0;
    while(it->con){
        //遍历整个链表
        NODE_STUDENT(stud,it);
        if(notf == 1 || liststu_suitable(stud)){
            //如果符合筛选条件
            node* nw = list_push_back(nodetype::stu,*newtail);
            NODE_STUDENT(s,nw);
            copyNode(nw,it);    //插入并复制节点
            s->recfiles = 0;    //设0防止源数据被删除
            n++;
        }

        it = it->next;
    }

    return n;
}

/**
 * @brief 链表的复制
 * @param srchead 源链表头
 * @param srctail 源链表尾
 * @param newhead 新链表头
 * @param newtail 新链表尾
 */
void form_Main::liststu_copy(node* srchead,node* srctail,node** newhead,node** newtail){
    liststu_find(srchead,srctail,newhead,newtail,1);
}

/**
 * @brief 从根节点开始查找与txt相等的文字的节点索引
 * @param root  根节点
 * @param txt   匹配的文字
 * @return 匹配到的索引 没找到返回-1
 */
int form_Main::findStrByRoot(QTreeWidgetItem* root,QString txt){
    for(int i = 0;i < root->childCount();++i){
        if(txt == root->child(i)->text(0))return i;
    }
    return -1;
}




/**
 * @brief 根据链表中的数据进行统计：
 * @details
 *      群体：（全体年级专业班级）
 *           人数，总分，平均分，最高分，最低分，极差，方差，中位数，9数据
 */
void form_Main::statstu_update(){
    //ui->cbo_stus_find   3专业 4年级 5班级 6正确率

    init_stat();                                        //统计表初始化
    auto yrsroot = ui->tree_stat->topLevelItem(0);      //年级
    auto majroot = ui->tree_stat->topLevelItem(1);      //专业
    auto clsroot = ui->tree_stat->topLevelItem(2);      //班级
    auto gcdroot = ui->tree_stat->topLevelItem(3);      //总体

    if(!liststu_head || !liststu_tail)return;
    if(liststu_head->next == liststu_tail)return;       //空链表处理

    node *it = liststu_head->next;                      //第一个有效节点
    while(it->con){
        NODE_STUDENT(reciv,it);
        addtosta(reciv->major,reciv->cls,reciv->year);  //散列并添加到统计表中
        QString mm;
        GETQSTRMB(mm,reciv->major);

        //开始装入数据
        int idx;

        idx = findStrByRoot(yrsroot,QString::number(reciv->year));                      //找节点
        if(idx != -1){
            //找到了
            auto qcb = (QComboBox*)ui->tree_stat->itemWidget(yrsroot->child(idx),9);    //找控件
            qcb->addItem(QString::number(reciv->grd));                                  //记录数据
        }

        idx = findStrByRoot(majroot,mm);
        if(idx != -1){
            auto qcb = (QComboBox*)ui->tree_stat->itemWidget(majroot->child(idx),9);
            qcb->addItem(QString::number(reciv->grd));
        }

        idx = findStrByRoot(clsroot,getclsname(reciv->major,reciv->cls,reciv->year));
        if(idx != -1){
            auto qcb = (QComboBox*)ui->tree_stat->itemWidget(clsroot->child(idx),9);
            qcb->addItem(QString::number(reciv->grd));
        }

        auto qcb = (QComboBox*)ui->tree_stat->itemWidget(gcdroot->child(0),9);
        qcb->addItem(QString::number(reciv->grd));

        it = it->next;
    }


    //统计完 开始计算人数，总分，平均分，最高分，最低分，极差，方差，标准差
    for(int i = 0;i < ui->tree_stat->topLevelItemCount();++i){
        auto r = ui->tree_stat->topLevelItem(i);
        for(int j = 0;j < r->childCount();++j){
            //遍历所有节点
            auto ch = r->child(j);
            auto qcb = (QComboBox*)ui->tree_stat->itemWidget(ch,9);
            int cnt = qcb->count();             //人数
            long long sum = 0;                  //总分
            double avg = 0,fc = 0,st = 0;       //平均分 方差 标准差
            int hs = INT_MIN,ls = INT_MAX;      //最高分 最低分

            //计算。。。
            for(int k = 0;k < cnt;++k){
                //遍历成绩
                int gg = qcb->itemText(k).toInt();
                sum += gg;
                hs = gg > hs ? gg : hs;
                ls = gg < ls ? gg : ls;
            }

            avg = 1.0 * sum / cnt;

            for(int k = 0;k < cnt;++k){
                int gg = qcb->itemText(k).toInt();
                fc += (gg - avg)*(gg - avg);
            }

            fc /= cnt;
            st = sqrt(fc);

            //显示。。
            ch->setText(1,QString::number(cnt));
            ch->setText(2,QString::number(sum));
            ch->setText(3,QString::number(avg));
            ch->setText(4,QString::number(hs));
            ch->setText(5,QString::number(ls));
            ch->setText(6,QString::number(hs - ls));
            ch->setText(7,QString::number(fc));
            ch->setText(8,QString::number(st));

        }

    }



}






//===============================================================================================================================================================================================================================================================


/**
 * @brief 保存编辑内容
 */
void form_Main::save_liststu(){
    char tt[2*FILENAME_MAX];
    getUserFilename(&this->set,FILENAME_STUDENTS,tt);
    FILE* fp = fopen(tt,"w");
    if(!fp){
        QMessageBox::critical(0,"保存文件失败","请勿占用students.csv!");
    }

    list_toFile_stu(fp,liststu_head,liststu_tail);      //转为文件格式
    fclose(fp);
}


/**
 * @brief 学生管理：点击 -- 删除
 */
void form_Main::onClick_tableDel(){


    auto sd = (QPushButton*)sender();
    int x = sd->geometry().x();
    int y = sd->geometry().y();
    auto itm = ui->table_stus->indexAt(QPoint(x,y));        //获取点击位置

    node* srck = findstu_row(liststu_head,liststu_tail,itm.row());
    node* fk = findstu_row(liststu_head_find,liststu_tail_find,itm.row());

    //删除
    if(QMessageBox::information(0,"删除","您确定要删除该学生数据？",QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes){
        list_delete(srck);             //删除节点
        list_delete(fk);
        if(liststu_n != 0)liststu_n--;                   //长度改变
        if(liststu_n_find != 0)liststu_n_find--;
        on_spin_stu_page_valueChanged(0);           //刷新
    }

    //重置查找链表
    this->liststu_findnext = this->liststu_head->next;         //指针节点置为头节点
    liststu_findmove = 0;

}

/**
 * @brief 学生管理：点击 -- 插入
 */
void form_Main::onClick_tableIns(){

    if(liststu_n == 0){
        //链表为空
        node* nw = list_insert_front(nodetype::stu,liststu_tail);
        NODE_STUDENT(st,nw);
        st->ID = 1,st->cls = 1,st->grd = 0,st->major[0] = 0,st->name[0] = 0,st->year = 0;
        st->recfiles = 0;
    }else{

        auto sd = (QPushButton*)sender();
        int x = sd->geometry().x();
        int y = sd->geometry().y();
        auto itm = ui->table_stus->indexAt(QPoint(x,y));        //获取点击位置
        int listn = calc_listn_row(itm.row());                  //计算节点在链表的第几个
        node* k = advanceNode(liststu_head->next,listn,0,0);    //找到这个节点
        node* nw = list_insert_front(nodetype::stu,k);          //插入新节点
        NODE_STUDENT(st,nw);
        copyNode(nw,k);                                         //复制内容
        st->ID = QRandomGenerator::global()->bounded(0,INT32_MAX);//防止重复
        st->recfiles = 0;                                       //保护资源
    }

    liststu_n++;

    on_spin_stu_page_valueChanged(0);                       //刷新
}


/**
 * @brief 选中：学生管理 -- 记录组合框
 * @param index 当前索引
 */
void form_Main::onSelect_tablecsvCombo(int index){
    if(index == 0)return;

    auto sd = (QComboBox*)sender();
    if(QMessageBox::question(0,"提示","要从系统打开它吗？") != QMessageBox::Yes)
        return;

    char buf[2*FILENAME_MAX] = {0};
    getUserFilename(&this->set,FILENAME_RECORDS,buf);
    QString path;
    GETQSTRMB(path,buf);
    path += "/" + sd->itemText(index);
    QProcess::execute("explorer",{path.replace("/","\\")});     //从系统打开它

}

/**
 * @brief form_Main::窗体显示
 * @param qse
 */
void form_Main::showEvent(QShowEvent* qse){
    drawMe();               //重画窗体
    connectAll();           //链接
}

/**
 * @brief form_Main::窗体关闭
 * @param qce
 */
void form_Main::closeEvent(QCloseEvent* qce){
    int ig = 0;
    switch(this->lginfo.user.type){
    case Student:
        break;
    case Teacher:
    {
        auto r = QMessageBox::question(0,"提示","要保存学生数据吗？",QMessageBox::Yes | QMessageBox::No | QMessageBox::Close);
        if(r == QMessageBox::Close)ig = 1;
        else if(r == QMessageBox::Yes)save_liststu();
    }   break;
    case Admin:
        break;
    case not_identified:
    case guest:
        break;
    }

    if(ig){
        qce->ignore();
    }
}

/**
 * @brief 接收作答窗口 -- 报告已经保存
 * @param htmlf 报告文件名
 * @param csvf 记录文件名
 */
void form_Main::get_save_grd(QString &htmlf,QString &csvf){

    long long submiter = this->lginfo.user.id;  //这个学生做了题
    node* k = findstu_ID(liststu_head,liststu_tail,submiter);
    if(!k)return;

    //插入记录链表
    NODE_STUDENT(stud,k);
    if(!stud)return;
    node *head = 0,*tail = 0;
    if(!stud->recfiles){
        //如果是空的 就创建空链表
        list_headtail(&head,&tail);
        stud->recfiles = head;
    }

    //找末尾。。。
    node* it = stud->recfiles->next;
    while(it->con)it = it->next;

    node* nw = list_push_back(nodetype::filename,it);   //插入

    NODE_FILENAME(ch,nw);
    if(ch)GETMBQSTR(ch,csvf);             //转码复制

    if(this->lginfo.user.type == usertype::Student){
        //如果是学生触发了这个信号 那么立刻保存
        save_liststu();
    }

    on_spin_stu_page_valueChanged(0);   //刷新
}

/**
 * @brief 做题窗口关闭
 */
void form_Main::get_qsted_closed(){
    this->show();       //显示
}


/**
 * @brief 录题--新建
 */
void form_Main::on_but_qst_new_clicked()
{
    fqst.setUserMsg(&this->set,&this->lginfo);  //传递用户信息
    fqst.newQEditor();      //按新建方式打开
    this->hide();
}

/**
 * @brief 录题--打开
 */
void form_Main::on_but_qst_open_clicked()
{
    fqst.setUserMsg(&this->set,&this->lginfo);
    fqst.openQDlg(1);       //按文件对话框打开
    this->hide();
}


/**
 * @brief 做题--文本编辑完毕
 */
void form_Main::on_txt_qst_path_editingFinished()
{
    displayfile_doqst();            //更新做题的题目
}


/**
 * @brief 做题 -- 双击文件
 * @param item  树节点指针
 * @param column 列
 */
void form_Main::on_tree_doqst_itemDoubleClicked(QTreeWidgetItem *item, int column)
{

    if(item->text(1) != "题目文件")return;

    QString s = item->text(0);      //获得文件路径
    auto p = item->parent();        //获得父节点
    if(p){
        //若有父节点 附上父节点的路径
        s = p->text(0) + "/" + s;
    }
    s =  ui->txt_qst_path->text() + "/" + s;


    fqst.setUserMsg(&this->set,&this->lginfo);

    fqst.openQPath(s,0);            //按路径打开做题器
    this->hide();



}

/**
 * @brief 学生管理--每页几行
 * @param arg1 行数
 */
void form_Main::on_spin_stu_rowspp_valueChanged(int arg1)
{
    on_spin_stu_page_valueChanged(arg1);
}

/**
 * @brief 学生管理--第几页 可做刷新函数使用
 * @param arg1
 */
void form_Main::on_spin_stu_page_valueChanged(int arg1)
{
    ui->table_stus->blockSignals(1);
    node *ihead,*itail,*srhead,*srtail,*nh,*nt;
    if(ui->cbo_stus_find->currentText() == "无筛选" || ui->but_stus_findctrl->text() == "停止查找"){
        //不筛选 或 整个表查找时 按页输出整个链表的结果
        ihead = liststu_head;
        itail = liststu_tail;
        ui->txt_stus_res->setText(QString("共%1条结果").arg(QString::number(liststu_n)));
    }else if(bOk1){
        //继续筛选
        ihead = liststu_head_ff;
        itail = liststu_tail_ff;
        ui->txt_stus_res->setText(QString("共%1条结果").arg(QString::number(liststu_n_ff)));
    }else{
        //筛选时 输出筛选后链表的结果
        ihead = liststu_head_find;
        itail = liststu_tail_find;
        ui->txt_stus_res->setText(QString("共%1条结果").arg(QString::number(liststu_n_find)));
    }

    if(ui->cbo_stus_sort->currentText() == "无排序" || ihead->next == itail){
        table_resize_stu(ihead,itail);                          //显示
    }else{
        int isd = ui->cbo_stus_sort->currentText() == "降序";
        int ce = ui->cbo_stus_find->currentIndex();
        int (*liststu_sorttable[2][10])(node* a,node* b) = {{0,liststu_ID_u,liststu_name_u,liststu_major_u,liststu_year_u,liststu_cls_u,liststu_grd_u,0,0,0},
                                                             {0,liststu_ID_d,liststu_name_d,liststu_major_d,liststu_year_d,liststu_cls_d,liststu_grd_d,0,0,0}};
        //转移表，若成绩、年级、班级相同按姓名升序排序
        int (*f)(node*,node*) = liststu_sorttable[isd][ce];    //选择一个排序函数

        if(!f){
            table_resize_stu(ihead,itail);          //按原来的显示
            return;
        }

        //复制链表准备排序
        liststu_copy(ihead,itail,&srhead,&srtail);

        //指定函数原地归并排序
        list_sort(srhead,srtail,f,&nh,&nt);

        table_resize_stu(nh,nt);                        //显示
        list_clear(nh);                                 //释放内存
    }

    ui->table_stus->blockSignals(0);
}

/**
 * @brief 学生管理--上一页
 */
void form_Main::on_but_stu_page_pre_clicked()
{
    ui->spin_stu_page->setValue(ui->spin_stu_page->value()-1);
}
/**
 * @brief 学生管理--下一页
 */
void form_Main::on_but_stu_page_next_clicked()
{
    ui->spin_stu_page->setValue(ui->spin_stu_page->value()+1);
}

/**
 * @brief 学生管理--首页
 */
void form_Main::on_but_stus_page_home_clicked()
{
    ui->spin_stu_page->setValue(1);
}


/**
 * @brief 学生管理--尾页
 */
void form_Main::on_but_stu_page_last_2_clicked()
{
    ui->spin_stu_page->setValue(tablestu_page);
}


/**
 * @brief 学生管理--普通单元格内容修改
 * @param row       行
 * @param column    列
 */
void form_Main::on_table_stus_cellChanged(int row, int column)
{
    node* srck = findstu_row(liststu_head,liststu_tail,row);
    node* fk = findstu_row(liststu_head_find,liststu_tail_find,row); //找到链表中的节点
    NODE_STUDENT(src,srck);
    NODE_STUDENT(finder,fk);                                    //提取学生数据

    replacestu_rc(src,row,column);                              //按行列修改
    replacestu_rc(finder,row,column);

    on_spin_stu_page_valueChanged(0);
}



/**
 * @brief 学生管理--范围筛选
 * @param arg1 组合框参数
 */
void form_Main::on_cbo_stus_find_currentTextChanged(const QString &arg1)
{
    if(arg1 == "清除筛选"){
        list_clear(liststu_head_ff);
        liststu_head_ff = liststu_tail_ff = 0;
        ui->cbo_stus_find->setItemText(0,"无筛选");
        ui->cbo_stus_find->setCurrentIndex(0);
    }
    if(arg1 == "无筛选"){
        on_spin_stu_page_valueChanged(0);                 //刷新
        ui->cbo_stus_sort->setDisabled(1);
        ui->txt_stus_find->setDisabled(1);
        ui->but_stus_findctrl->setDisabled(1);
        ui->but_stus_addfind->setDisabled(1);
        ui->txt_stus_find->setText("");
        ui->txt_stus_find->setPlaceholderText("条件输入框");
        bOk1 = 0;
        return;
    }

    ui->cbo_stus_sort->setDisabled(0);
    ui->txt_stus_find->setDisabled(0);
    if(!bOk1)ui->but_stus_findctrl->setDisabled(0);     //继续筛选下不能查找
    ui->but_stus_addfind->setDisabled(0);

    if(arg1 == "姓名" || arg1 == "专业"){
        //文本查找
        ui->txt_stus_find->setPlaceholderText("输入文本进行模糊查找");
    }else{
        //数值区间查找
        ui->txt_stus_find->setPlaceholderText("输入a b(如12 66)进行区间查找");
    }

    on_txt_stus_find_textChanged(ui->txt_stus_find->text());//通过响应显示学生表

}


/**
 * @brief 学生管理--筛选文字改变
 * @param arg1 文字
 */
void form_Main::on_txt_stus_find_textChanged(const QString &arg1)
{

    if(!bOk1){
        //按原来的筛选
        list_clear(liststu_head_find);      //清空原来的
        liststu_n_find = liststu_find(liststu_head,liststu_tail,&liststu_head_find,&liststu_tail_find);
    }else{
        //继续筛选
        list_clear(liststu_head_ff);
        liststu_n_ff = liststu_find(liststu_head_find,liststu_tail_find,&liststu_head_ff,&liststu_tail_ff);
    }

    on_spin_stu_page_valueChanged(0);//显示结果
}

/**
 * @brief 学生管理--查找控制按钮
 */
void form_Main::on_but_stus_findctrl_clicked()
{
    if(ui->spin_stu_page->value() <= 0){
        QMessageBox::warning(0,"警告","没有数据");
        return;
    }

    if(ui->but_stus_findctrl->text() == "开始查找"){
        //开始查找
        ui->cbo_stus_sort->setCurrentIndex(0);
        ui->but_stus_findctrl->setText("停止查找");
    }else{
        //停止查找
        ui->but_stus_findctrl->setText("开始查找");
    }

    QTSWITCHENABLED(ui->but_stus_findnext);          //翻转控件可用性
    QTSWITCHENABLED(ui->cbo_stus_find);
    QTSWITCHENABLED(ui->cbo_stus_sort);
    QTSWITCHENABLED(ui->txt_stus_find);

    //重置
    this->liststu_findnext = this->liststu_head->next;         //指针节点置为头节点
    liststu_findmove = 0;
    on_spin_stu_page_valueChanged(0);                          //刷新
}

/**
 * @brief 学生管理--查找下一个
 */
void form_Main::on_but_stus_findnext_clicked()
{
    int step = 0;
    node* it = liststu_findnext;
    while(it->con){
        NODE_STUDENT(stud,it);

        if(liststu_suitable(stud)){
            //如果符合筛选条件
            //qDebug() << stud->ID;
            step++;
            break;
        }
        it = it->next;
        step++;
    }

    if(it->con == 0){
        QMessageBox::information(0,"提示","查找完毕！");
        on_but_stus_findctrl_clicked();     //停止
    }
    else liststu_findnext = it->next;   //继续走


    liststu_findmove += step;

    int pg = 0,rw = 0;      //计算在第几页第几行
    int rpp = ui->spin_stu_rowspp->value();
    int ip = ui->spin_stu_page->value();

    //找规律
    if(liststu_findmove % rpp == 0){
        pg = liststu_findmove / rpp - 1;
        rw = rpp;
    }else{
        pg = liststu_findmove / rpp;
        rw = ((liststu_findmove - ip*rpp) % rpp + rpp) % rpp;   //化为模的正整数
    }

    ui->spin_stu_page->setValue(pg+1);            //跳转到相应页数
    ui->table_stus->selectRow(rw-1);            //选中此行

}

/**
 * @brief form_Main::on_cbo_stus_sort_currentIndexChanged
 * @param index
 */
void form_Main::on_cbo_stus_sort_currentIndexChanged(int index)
{
    on_spin_stu_page_valueChanged(0);//显示结果
}

/**
 * @brief form_Main::on_but_stus_addfind_clicked
 */
void form_Main::on_but_stus_addfind_clicked()
{
    if(!bOk1)QMessageBox::information(0,"继续筛选","继续筛选可以在原来的筛选结果上进一步筛选 实现多字段查询\n当将查询字段置为\"清除筛选\"时可以复原为原来的状态");
    bOk1 = 1;

    liststu_copy(liststu_head_ff,liststu_tail_ff,&liststu_head_find,&liststu_tail_find);    //准备下一次筛选

    ui->cbo_stus_find->setCurrentIndex(1);
    ui->cbo_stus_find->setItemText(0,"清除筛选");
    ui->txt_stus_find->setText("");
    ui->but_stus_findctrl->setDisabled(1);

}


/**
 * @brief form_Main::on_txt_stat_find_textChanged
 * @param arg1
 */
void form_Main::on_txt_stat_find_textChanged(const QString &arg1)
{

    tree_findres = ui->tree_stat->findItems(ui->txt_stat_find->text(),Qt::MatchContains | Qt::MatchRecursive);
    if(tree_findres.size())ui->tree_stat->setCurrentItem(tree_findres[0]);

    // if(ui->but_stat_find->text() == "查找下一个"){
    //     //开始查找
    //     if(tree_stat_findit >= tree_stat_findres.size()){
    //         //找完了
    //         //停止
    //         ui->but_stat_find->setText("查找文本");
    //         tree_stat_findit = 0;
    //         return;
    //     }
    //     auto i = tree_stat_findres[tree_stat_findit++];
    //     ui->tree_stat->setCurrentItem(i);
    // }
}

/**
 * @brief form_Main::on_but_stat_find_clicked
 */
void form_Main::on_but_stat_find_clicked()
{
    // if(ui->but_stat_find->text() == "查找文本"){
    //     ui->but_stat_find->setText("查找下一个");
    //     tree_stat_findres = ui->tree_stat->findItems(ui->txt_stat_find->text(),Qt::MatchContains | Qt::MatchRecursive);
    //     tree_stat_findit = 0;
    // }
    // on_txt_stat_find_textChanged("");
}


/**
 * @brief form_Main::on_but_stat_update_clicked
 */
void form_Main::on_but_stat_update_clicked()
{
    statstu_update();       //刷新统计表
}


/**
 * @brief form_Main::on_txt_qst_find_textChanged
 * @param arg1
 */
void form_Main::on_txt_qst_find_textChanged(const QString &arg1)
{
    tree_findres = ui->tree_doqst->findItems(ui->txt_qst_find->text(),Qt::MatchContains | Qt::MatchRecursive);
    if(tree_findres.size())ui->tree_doqst->setCurrentItem(tree_findres[0]);
}

/**
 * @brief form_Main::on_but_stus_opendef_clicked
 */
void form_Main::on_but_stus_opendef_clicked()
{
    openUserFile_system(&this->set,FILENAME_STUDENTS);          //以系统方式打开students.csv
}

void form_Main::on_table_stus_cellActivated(int row, int column)
{

}


void form_Main::on_table_stus_cellEntered(int row, int column)
{

}

